GO
1. 什么是Shell
Shell是一个命令解释器,它的作用是解释执行用户输入的命令及程序,用户每输入一条命令,Shell就解释执行一条。这种从键盘一直输入命令,就可以立即得到回应的对话方式,称为交互的方式。
Shell存在于操作系统的最外层,负责与用户直接对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,然后输出到屏幕返回给用户。输入系统用户名和密码并登陆到Linux后的所有操作都是由Shell解释与执行的。
2. 什么是Shell脚本
当命令或程序语句不在命令行下执行,而是通过一个程序文件来执行时,该程序就被称为Shell脚本。如果在Shell脚本里内置了很多条命令、语句及循环控制,然后将这些命令一次性执行完毕,这种通过文件执行脚本的方式称为非交互的方式。用户可以在Shell脚本中敲入一系列的命令及命令语句组合。这些命令、变量和流程控制语句等有机地结合起来,就形成了一个功能强大的Shell脚本。
下面是一个Shell脚本的例子:清除/var/log下messages日志文件的简单命令脚本:
3. Shell脚本在Linux运维工作中的地位
Shell脚本语言很适合用于处理纯文本类型的数据,而Linux系统中几乎所有的配置文件、日志文件(如NFS、Rsync、Httpd、Nginx、LVS、MySQL等),以及绝大多数的启动文件都是纯文本类型的文件。因此,学号Shell脚本语言,就可以利用它在Linux系统中发挥巨大的作用。
4. 脚本语言的种类
4.1. Shell脚本语言的种类
Shell脚本语言是弱类型语言(无需定义变量的类型即可使用),在Unix/Linux中主要有两大类Shell:一类是Bourne Shell,另一类是C Shell。
Bourne Shell
Bounrn Shell又包括Bourne Shell(sh)、Korn Shell(ksh)、Bourne Agaim Shell(bash)三种类型。
- Bourne Shell(sh)由AT&T的Steve Bourne开发,是标准的UNIX Shell,很多UNIX系统都配有sh。
- Korn Shell(ksh)由David Korn开发,是Bourne Shell(sh)的超集合,并且添加了csh引入的新功能,是目前很多UNIX系统标准配置的Shell,这些系统上的/bin/sh往往是指向/bin/ksh的符号链接。
- Bourne Again Shell(bash)由GUN项目组开发,主要目标是与POSIX标准保持一致,同时兼顾对sh的兼容,bash从csh和ksh借鉴了很多功能,是各种Linux发行版默认配置的Shell,Linux系统上的/bin/sh往往是指向/bin/bash的符号链接。尽管如此,bash和sh还是有很多的不同之处:一方面,bash扩展了一些命令和参数;另一方面,bsh并不完全和sh兼容,它们有些行为并不一致,但在大多数企业运维的情况下区别不大,特殊场景可以使用bash替代sh。
C Shell
C Shell又包括csh、tcsh两种类型。
- csh由Berkeley大学开发,随BSD UNIX发布,它的流程控制语句很像C语言,支持很多Boruen Shell所不支持的功能,例如:作业控制、别名、系统算术、命令历史、命令行编辑等。
- tcsh是csh的增强版,加入了命令补全等功能,在FreeBSD、Mac OS X等系统上替代了csh。
以上介绍的这些Shell中,较为通用的是标准的Bourne Shell(sh)和C Shell(csh)。其中Bourne Shell(sh)已经被Bouren Again Shell(bash)所取代。
可以通过下面的命令查看CentOS系统的Shell支持情况:
Linux系统中的主流Shell是bash,bash是由Bourne Shell(sh)发展而来的,同时bash还包含了csh和ksh的特色,但大多数脚本都可以不加修改地在sh上运行,如果使用了sh后发现结果和预期有差异,那么可以尝试用bash替代sh。
4.2. 其它常用的脚本语言种类
1. PHP语言
PHP是网页程序语言,也是脚本语言。它是一款更专注于Web页面开发(前端展示)的语言,例如:wordpress、dedecmd、discus等著名的开源产品都是用PHP语言开发的。用PHP程序语言也可以处理系统日志、配置文件等,还可以调用Linux系统命令,但是,很少有人这么用。
2. Perl语言
Perl脚本语言比Shell脚本语言强大很多,在2010年以前很流行,它的语法灵活、复杂,在实现不同的功能时可以用多种不同的方式,缺点是不易读,团队协作困难,但它仍不失为一种很好的脚本语言,存世的大量相关程序软件(比如,xtrabackup热备工具、MySQL MHA集群高可用软件等)中都有Perl语言的身影。当下的Linux运维人员机会不需要了解Perl语言了,最多可了解一些Perl语言的安装环境。当然了想要二次开发用Perl编写软件人员例外,Perl语言已经称为历史了。
3. Python语言
Python是近几年非常流行的语言,它不但可以用于脚本程序开发,也可以实现Web页面程序开发(例如:CMDB管理系统),甚至还可以实现软件的开发(例如:大名鼎鼎的OpenStack、SalStack都是Python语言开发的)、游戏开发、大数据开大、移动端开大。
现在越来越多的公司都要求运维人员会Python自动化开发。Python语言目前是全球第四大开发语言,未来的发展前景很好,每一个运维人员在掌握了Shell编程之后,都应该深入学习Python语言,以提升职场竞争力。
4.3. Shell脚本语言的优势
Shell脚本语言的优势在于处理偏操作系统底层的业务,例如:Linux系统内部的很多应用(—有的是应用的一部分)都是使用Shell脚本语言开发的,因为有1000多个Linux系统命令为它做支撑,特别是Linux正则表达式和三剑客grep、awk、sed等命令。
对于一些常见的系统脚本,使用Shell开发会更简单、更快速,例如:让软件一键自动化安装、优化、监控报警脚本。软件启动脚本,日志分析脚本等,虽然PHP/Python语言也能够做到这些,但是,考虑到掌握难度、开发效率、开发习惯等因素,它们可能就不如Shell脚本语言流行及有优势了。对于一些常规的业务应用,使用Shell更符合Linux运维简单、易用、高效的三大基本原则。
PHP语言的优势在于小型网站系统的开发;Python余元的优势在于开发较复杂的运维工具软件、Web界面的管理工具和Web业务的开发等。(例如:CMD自动化运维平台、跳板机、批量管理软件SaltStack、云计算OpenStack软件等)。我们在开发一个应用时应根据业务需求,结合不通语言的优势及自身擅长的语言来选择,扬长避短,从而达到高效开发及易于自身维护等目的。
5. 常用操作系统默认的Shell
在常用的操作系统中,Linux下默认的Shell是bash;Solaris和FreeBSD下默认的是sh;AIX下默认的是ksh。这里重点讲Linxu系统环境下的bash。
这是一道企业面试题:CentOS Linux系统默认的Shell是什么?这题的答案就是bash。
可以通过一下两种方法查看CentOS Linux 系统默认的Shell。
方法1:
12[root@theshu ~]/bin/bash方法2:
123[root@theshu ~]# grep root /etc/passwdroot:x:0:0:root:/root:/bin/bash# 提示:结尾的/bin/bash就是用户登陆后的Shell解释器。
6. Shell脚本的建立和执行
6.1. Shell脚本的建立
在Linux系统中,Shell脚本(bash shell程序)通常是在编辑器vi/vim中编写的,由Unix/Linux命令、bash Shell命令、程序结构控制语句和注释等内容组成。这里推荐用Vim工具。
1. 脚本开头(第一行)
一个规范的Shell脚本在第一行会指出由哪个程序(解释器)来执行脚本中的内容,这一行内容在Linux Bash的编程一般为:
其中,开头的#!字符又称为幻数(其实叫什么都无所谓,知道它的作用就好),在执行bash脚本的时候,内核会根据#!后的解释器来确定该用哪个程序解释这个脚本中的内容。
注意,这一行必须位于每个脚本顶端的第一行,如果不是第一行则为脚本注释行,例如下面的例子:
2. bash与sh的区别
早期的bash与sh稍有不同,它还包含了csh和ksh的特色,但大多数脚本都可以不加修改地在sh上运行,比如:
提示:
- sh为bash的软链接,大多数情况下,脚本的开头使用
#!/bin/bash
和#!/bin/sh
是没有区别的,但更规范的写法是在脚本的开头使用#!/bin/bash
- 如果使用
/bin/sh
执行脚本出现异常,那么可以再使用/bin/bash
试一试,但是一般不会发生此类情况。
一般情况下,在安装Linux系统时会自动安装好bash软件,查看系统的bash版本的命令如下:
如果读者使用的是较老版本的Shell,那么建议将其升级到最新版本的Shell,特别是企业使用,因为近两年老版本的bash被曝露出存在较为严重的安全漏洞。
例如:bash软件曾经爆出了严重漏洞(破壳漏洞),凭借此漏洞,攻击者可能会接管计算机的整个操作系统,得以访问各种系统内的机密信息,并对系统进行更改等。任何人的计算机系统,如果使用了bash软件,都需要立即打上补丁。检测系统是否存在漏洞的方法为:
如果返回如下两行,则表示需要尽快升级bash了,不过,仅仅是用于学习和测试就无所谓了。
升级方法为:
提示:如果没有输出be careful,则不需要升级。
下面是Linux中常用脚本开头的写法,不同语言的脚本在开头一般都要加上如下标识内容:
CentOS和RedHat Linux下默认的Shell均为bash。因此,在写Shell脚本的时候,脚本的开头即使不加#!/bin/bash
,它也会交给bash解释。如果写脚本不希望使用系统默认的Shell解释,那么就必须要指定解释器了,否则脚本文件执行后的结果可能就不是你所要的。建议养成良好的编程习惯,不管采用什么脚本,最好都加上相应的开头解释器语言标识,遵守Shell编程规范。
如果在脚本开头的第一行不指定解释器,那么就要用对应的解释器来执行脚本,这样才能确保脚本正确执行。例如:
- 如果是Shell脚本,就用
bash test.sh
执行test.sh - 如果是Python脚本,就用
python test.py
执行test.py - 如果是expect脚本,就用
expect test.exp
执行test.exp - 其他的脚本程序大都是类似的执行方法
3. 脚本注释
在Shell脚本中,跟在#
后面的内容表示注释,用来对脚本进行注释说明,注释部分不会被当作程序来执行,仅仅是给开发者和使用者看的,系统解释器是看不到的,更不会执行。注释可自成一行,也可以跟在脚本命令后面与命令在同一行。开发脚本时,如果没有注释,那么团队里的其他人就会很难理解脚本对应内容的用途,而且若时间长了,自己也会忘记。因此,我们要尽量养成所开发的Shell脚本书写关键注释的习惯,书写注释不光是为了方便别人,更是为了方便自己,避免影响团队的写作效率,以及给后来接手的人带来维护困难。特别提示一下,注释尽量不要用中文,在脚本中最好也不要有中文。
6.2. Shell脚本的执行
1. 通常情况下shell执行过程
当Shell脚本运行时,它会先查找系统环境变量ENV,该变量制定了环境文件(加载顺序通常是/etc/profile
、~/.bash_profile
、~/.bashrc
、/etc/bashrc
等),在加载了上述环境变量文件后,Shell就开始执行Shell脚本中的内容(更多Shell加载环境变量的知识需要看Shell学习笔记3)。
Shell脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在Shell脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。
通常情况下,在执行Shell脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子Shell脚本。
特殊技巧: 设置Linux的crond任务时,最好能在定时任务脚本中重新定义系统环境变量,否则,一些系统环境变量将不会被加载,这个问题需要注意!
2. Shell的几种执行方式
Shell脚本的执行通常可以采用以下几种方式:
bash script-name
或sh script-name
:这是当脚本文件本身没有可执行权限时常用的方法,或者脚本文件开头没有指定解释器时需要使用的方法。这也是推荐使用的方法。path/script-name
或./script-name
:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行(即文件权限属性加x位),具体方法为chmod +x script-name
。然后通过脚本的绝对路径或相对路径就可以直接执行脚本了。(在企业生产环境中,不少运维人员在写完Shell脚本后,由于忘记为该脚本设置执行权限,然后就直接应用了,结果导致脚本没有按照自己的意愿手动或定时执行,对于这一点,避免出现该问题的方法就是用第1种方法代替第2种)。source script-name
或. script-name
:这种方法通常是使用source
或.(点号)
读入或加载指定的Shell脚本文件(如son.sh),然后,依次执行指定的Shell脚本son.sh中所有语句。这些语句将在父Shell脚本father.sh进程中运行(其它几种模式都会启动新的进程执行子脚本)。因此,使用source
或.
可以将son.sh自身脚本中的变量值或函数等的返回值传递到当前父Shell脚本father.sh中使用。这是它和其它几种方法最大的区别,这也是值得特别注意的地方。(source
或.
命令的功能是:在当前Shell中执行source
或.
加载并执行的相关脚本文件中的命令及语句,而不是产生一个子Shell来执行文件中的命令。注意,.
和后面的脚本名之间要有空格。如果学过PHP开发就会明白,source
或.
相当于include的功能。HTTP服务软件Apache、Nginx等配置文件里都支持这样的用法)。sh < script-name
或cat script-name | sh
:同样适用于bash,不过这种用法不是很常见,但有时也可以有出奇制胜的效果,例如:不用循环语句来实现精简开机自启动服务的案例,就是通过将所有字符串拼接为命令的形式,然后经由管道交给bash操作的案例。
3. 示例说明
创建模拟脚本test.sh,并输入如下内容:
输入”echo ‘I am theshu’”内容后按回车键,然后再按Ctrl+D组合键结束编辑。此操作为特殊编辑方法,这里是作为cat用法的扩展知识。
第一种方法实践:
第二种方法实践:
虽然没有权限的test.sh脚本不能直接被执行,但是可以用source
或.(点号)
来执行,它俩的功能相同,都是读入脚本并执行脚本。如下:
给test.sh添加可执行权限,命令如下:
可以看到,给test.sh加完可执行权限后就能执行了。前面也提到了,这种方法在使用前每次都需要给定执行权限,但容易忘记,且多了一些步骤,增加了复杂性。
第三种方法实践:会将source
或.
执行的脚本中的变量值传递到当前的Shell中,如下:
以上步骤说明了,用第三种方法会使脚本中的变量值传递到当前的shell中。
提示:操作系统及服务自带的脚本是我们学习的标杆和参考(虽然有时感觉这些脚本也不是十分规范)
结论:通过source
或.
加载执行过的脚本,由于是在当前Shell中执行脚本,因此在脚本结束之后,脚本中的变量(包括函数)值在当前Shell中依然存在,而sh和bash执行脚本都会启动新的子Shell执行,执行完后退回到父Shell。因此,变量(包括函数)值等无法保留。在进行Shell脚本开发时,如果脚本中有引用或执行其他脚本的内容或配置文件的需求时,最好用.
或source
先加载到该脚本或配置文件,处理完之后,再将它们加载到脚本的下面,就可以调用source加载的脚本及配置文件中的变量及函数等内容了。
第四种方法实践:
提示:代码中提到的两种执行方法相当于
sh script-name
,效率很高,但是初学者用得少。
下面再看一道面试题:
答案是c。经过上述面试题可以得到如下的结论:
- 子Shell脚本会直接继承父Shell脚本的变量、函数等,反之则不可。
- 如果希望反过来继承(即父Shell继承子Shell的),就要用
source
或.
在父Shell脚本中事先加载子Shell脚本。
6.3. Shell脚本开发的基本规范及习惯
Shell脚本的开发规范及习惯非常重要,虽然这些规范不是必须要遵守的,但有了好的规范和习惯,可以大大提升开发效率,并能在后期降低对脚本的维护成本。当多人写作开发时,大家有一个互相遵守的规范就显得更重要了。即使只是一个人开发,最好也采取一套固定的规范,这样脚本将会更易读、更易于后期维护,最重要的是要让自己养成一个一出手就很专业和规范的习惯。
规范
下面来看看有哪些规范,这些规范在Shell学习笔记14中也会提及,以便更进一步巩固。
Shell脚本的第一行是指定脚本解释器,通常为:
123或Shell脚本的开头会加版本、版权等信息:
- Shell脚本中尽量不用中文(不限于注释)
- 尽量用英文注释,防止本机或切换系统环境后中文乱码的困扰。
- 如果非要加中文,请根据自身的客户端对系统进行字符集调整,如
export LANG="zh_CN.UTF-8"
,并在脚本中,重新定义字符集设置,和系统保持一致。
- Shell脚本中的命令应以.sh为扩展名,例如:
script-name.sh
- Shell脚本应存放在固定的路径下,例如:
/server/scripts
书写的良好习惯
以下是Shell脚本代码书写的良好习惯:
- 成对的符号应尽量一次性写出来,然后退格在符号里增加内容,以防止遗漏。这些成对的符号包括:
{}、[]、''、""等
- 中括号([])两端至少要有1个空格,因此,键入中括号时即可留出空格,然后再退格键入中间的内容,并确保两端都至少有一个空格,即先键入一对中括号,然后退1格,输入两个空格,再退1格,双中括号([[]])的写法也是如此。
对于流程控制语句,应一次性将格式写完,再添加内容。
比如,一次性完成if语句的格式,应为:
1234if 条件内容then内容fi一次性完成for循环语句的格式,应为:
1234fordo内容donewhile和until,case等语句也是一样。
- 通过缩进让代码更易读
- 对于常规变量的字符串定义变量值应加双引号,并且等号前后不能有空格,需要强引用的(指所见即所得的字符引用),则用单引号,如果是命令的引用,则用反引号。
- 脚本中的单引号、双引号及反引号必须为英文状态下的符号,其实所有的Linux字符及符号都应该是英文状态下的符号,这点需要特别注意。
说明:好的习惯可以让我们避免很多不必要的麻烦,提升工作效率。
OK